参考golang手册,记录数据类型相关资料;方便日后来回顾基础知识。
基本数据
布尔
布尔类型,表示真与假。true|false
整型/浮点数
数值类型,分为整形和浮点类型,下面列举的是类型,以及读数的区间。
1 | uint8 the set of all unsigned 8-bit integers (0 to 255) |
整数类型按照字节数来分类,有8位、16位、32位、64位。每个数字的区间范围、内存空间都不一样。
int,uint会按照cpu字节大小最终是32位或者64位。Unicode.rune和int32等价,byte和uint8等价。
unsafe包里面提供的uintptr,不会规定内存的size,但是能容纳从c语言传递过来的内存指针。
整数
最高位使用了反码方式来表示。拿8位来举例子。他的取值范围是(-128 to 127)
-128= 二进制 1111 1111
127= 二进制 0111 1111
对有符号数字做位操作时,向右移将会自动补上符号位,向左移符号位不会受到影响。
1 | var a int8 |
1 | -0000001 |
这个特性需要注意,一般在操作位的时候,我们都偏向使用无符号的数字来处理。
在处理循环的时候,最好推荐使用有符号的变量来处理。防止程序出现问题。
原始的数据:
“0xffffffffffffffffffffffffffffffffffffffffffffffffbc705c3c8afed1d”
转换成 int256 :
-4868289782087757360
通过搜索 golang int256
发现了这个资料
1 | func t1() { |
无符号
将会把全部的bit位当成数值来计算。在做位操作时,可以当成数组来理解。
整数类型的运算优先级(行里面是级别相等,从上往下的列,级别是依次降低):
1 | * / % << >> & &^ |
浮点类型
1 |
|
提供了 float32 float64 两种。IEEE 754标准。
所有极限值被定义在math包中。
1 | math.MaxFloat32 |
float32 大约能理解成6个十进制的精度;float64能到15个十进制精度。
时间
1 | // 将字符串转换成时间 |
设置时区
1 | //init the loc |
将字符串转换成时间戳
1 | package main |
String
string存储了一系列的bytes,string的字节数是就是字符串的长度,而且不可能为负数。当创建了字符串之后,将不会发生改变。可以使用内建函数len取得字符串的长度。可以通过下标方式取得字符串的数值。
可以通过操作,可以直接分离出内容
1 | str := "0123456" |
不允许对字符串中间的内容做任何修改。
1 | .\main.go:24:9: cannot assign to str[2] |
转移方式:
\x11ff 十六进制
\o1188 八进制
原生字符串,一般用于制作正则表达式。使用反引号。
1 | const GoUsage=`Go is a tool for managing xxx |
unicode字符集,参考主页:http://unicode.org
golang使用的是utf32或者UCS-4,每个码点占用32bit。
utf-8
ascii部分是使用1个byte,常用字使用2或3个字符来表示。
解析的时候,读取高位来判断是采取几个字节(二进制)来加载。
高位为0,就是ascii,使用1个byte
0xxxxxxx
高位为110,需要使用2个byte
110xxxxx 10xxxxxx
高位为1110,需要使用3个byte
1110xxxx 10xxxxxx 10xxxxxx
高位为11110,需要使用4个byte
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
这样能完全兼容ascii码,而且能支持字符的压缩。
unicode/utf8包中,RuneCountInString能量出来utf8字符长度。
string相关的包 bytes,strings,strconv,unicode
strings包,主要提供查找、替换、比较、阶段、拆分和合并功能;
bytes包,用于处理[]byte的库,类似于strings。由于string是不能修改的,所以一般加工字符串的时候,都通过转换成[]byte来做处理。
string和[]byte的转换。
1 | bytes := []byte("I am byte array !") |
strconv包,用于将布尔,整型,浮点与string之间做转换;
unicode包,用于处理utf8相关操作。
中文处理
1 | "golang.org/x/text/encoding/simplifiedchinese" |
const
测试实例
1 | package learngotest |
复合数据类型
数组
数组一般都是指定长度的某种类型 。由于长度无法变化,其实很少使用到。
1 | var a[3] int |
数组能直接做==的检测判断。
在调用函数的时候,将会赋值给函数的内部变量,如果参数使用了数组,将会存在内存拷贝。如果传递大的数组,将会有性能消耗。而且在修改数组内容时,也是在复制的数组中修改。如果想减少消耗,并且支持同步修改,还是需要使用数组指针方式来传递。
slice
切片是可变长的数组。每个元素都是相同类型。它包含了指针、长度和容量。指针是指向自己持有的slice的第一个元素,不一定是真实的数组中的第一个。长度,就是当前slice的长度。容积是全部长度。slice可以通过切片方法,分离出子串。其实本质上,他们还是指向同一个数组的。只是由于他们的指针位置不同,而体现出来不一样。
切片的方法a[n:m]
将会取出[n,m)
之间的元素出来,产生新的slice。
注意数字就是使用的下表,从0开始
。
如果n没有填写,默认意思是0。
m没有填写,默认就是切片的长度。
常见用法:a[:]
获取全部内容。a[:0]
清理掉全部的成员=[0,0)
;s = s[:len(s)-1]
抓取最后一个元素;s = s[len(s)-1:]
删除最后一个元素;
1 | make([]T,len,[cap]) |
append函数为slice追加内容。如果cap足够,那就直接添加到尾部,否则将重新创数组,将老数组copy进去,将新的内容添加到数组尾部。append都是返回一个新的数组出来,而不会去直接修改老数组。
slice类似这样的数据结构:
1 | type IntSlice struct { |
在使用 slice 的时候,需要注意底层存在一个数组。如果能复用,将会大大的提高运行效率。
1 | func main() { |
需要做一次内存拷贝。如果不在乎顺序,可以直接将最后位置的元素,直接赋值掉指定位置的元素,删除slice最后的元素。
在stackoverflow里面的建议是直接使用连接两个拆分的slice,这样反而会比较高效。
1 | func remove(slice []int, s int) []int { |
slice在循环的时候,删除一个元素golang slice delete while iterating
2018-11-13golang slice遍历删除-go语言中文网
2016-07-25Modifying a Go slice in-place during iteration
1 | var x = []int{90, 15, 81, 87, 47, 59, 81, 18, 25, 40, 56, 8} |
1 | package main |
输出 after del:[7 7 8]
获取高赞的处理方法,第一遍是将全部有用节点排列到<i
的数组里面,[i,j)
之间的内存都做一次强制的设置成空。s = s[:i]
就是将slice区域重置。
1 | i := 0 // output index |
注意,这将在底层数组中的索引i之后保留旧值,因此如果值是或包含指针,这将泄漏内存,直到片本身被垃圾收集。您可以通过将所有值设置为nil或从i到切片结束的零值来解决此问题,然后再截断切片。这个警告适用于说第二步骤需要小心。
连接两个slice
,使用可变长函数。
1 | func TestSliceJoin(lst ...int)[]int { |
检查某个元素是否在其中
Golang 1.18 引入了 slices
包,它提供了一组方便的函数来操作切片,使得常见的切片操作更加简洁和高效。以下是一些 slices
包中的主要功能:
Contains: 检查切片是否包含某个元素。
1
2
3
4
5
6
7
8
9
10
11
12package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{0, 42, 10, 8}
containsTen := slices.Contains(numbers, 10) // true
fmt.Println(containsTen)
}Equal: 检查两个切片是否相等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14package main
import (
"fmt"
"slices"
)
func main() {
a := []int{1, 2, 3}
b := []int{1, 2, 3}
c := []int{1, 2, 4}
fmt.Println(slices.Equal(a, b)) // true
fmt.Println(slices.Equal(a, c)) // false
}Clone: 创建一个切片的副本。
1
2
3
4
5
6
7
8
9
10
11
12package main
import (
"fmt"
"slices"
)
func main() {
a := []int{1, 2, 3}
b := slices.Clone(a)
fmt.Println(b) // [1, 2, 3]
}Index: 查找某个元素在切片中的索引。
1
2
3
4
5
6
7
8
9
10
11
12package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{0, 42, 10, 8}
index := slices.Index(numbers, 10) // 2
fmt.Println(index)
}Replace: 替换切片中的一部分内容。
1
2
3
4
5
6
7
8
9
10
11
12
13package main
import (
"fmt"
"slices"
)
func main() {
a := []int{1, 2, 3, 4, 5}
b := []int{9, 9, 9}
slices.Replace(a, 1, 4, b...)
fmt.Println(a) // [1, 9, 9, 9, 5]
}Sort: 对切片进行排序。
1
2
3
4
5
6
7
8
9
10
11
12package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{4, 2, 3, 1}
slices.Sort(numbers)
fmt.Println(numbers) // [1, 2, 3, 4]
}BinarySearch: 在已排序的切片中进行二分查找。
1
2
3
4
5
6
7
8
9
10
11
12package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{1, 2, 3, 4, 5}
index, found := slices.BinarySearch(numbers, 3)
fmt.Println(index, found) // 2 true
}Delete: 删除切片中的一个元素。
1
2
3
4
5
6
7
8
9
10
11
12package main
import (
"fmt"
"slices"
)
func main() {
a := []int{1, 2, 3, 4, 5}
a = slices.Delete(a, 1, 3)
fmt.Println(a) // [1, 4, 5]
}
这些函数使得操作切片变得更加方便和高效,减少了手动编写重复代码的需要。如果你需要更多信息,可以参考官方文档了解更多细节。
map
key/value方式无序的集合。golang中的map其实就是哈希表。其中key的类型需要支持==比较运算符。浮点型最好不好当成key。
创建map的方法
1 | ages :=make(map[string]int) |
map的元素可以通过key下标来访问
1 | ages["alice"]=32 |
通过delete对map中的元素删除,即使不存在也能删除。
1 | delete(ages,"alice") |
在循环map的时候,删除keys是否安全。Is it safe to remove selected keys from map within a range loop?
1 | for key := range m { |
也能直接对不存在的元素直接做操作
1 | ages["bob"] = ages["bob"]+1 |
系统会自动初始化key为bob的元素并且返回0
map中的元素不是变量,所以无法取得地址。原因也是由于随着元素的删减,可能内存空间有放大,或者缩小。所以取得的内存地址可能失效。
循环方式是
1 | for name,age := range ages { |
由于是hash所以遍历的顺序是不固定的。map变量定义出来之后都是nil值。可以通过make语句来创建初始化。
如需要检查一个元素是否有初始化,可以通过
1 | age, ok := ages["bob"] |
map容器不能直接做比较操作。
go语言没有提供set类型,可以使用map来代替。
(golang-sort-map-by-key)[https://aguidehub.com/blog/2022-11-11-golang-sort-map-by-key/]
1 | package main |
结构体
结构体里面可以定义0-n个成员。
1 | type T struct { |
如果函数需要修改结构体内容,出于对效率的考虑,一般都是使用结构体的引用作为参数来传递。
结构体的嵌套
1 | type Point struct { |
go语言中能再struct中定义匿名的成员,这样就直接将其他的struct直接嵌入到了自己的结构体
1 | type Circle struct { |
struct可以通过
1 | fmt.Printf("%#v\n",w) |
输出信息。
1 | // 初始化对象的写法 |
16进制数据转换
1 | data, err := hex.DecodeString(txHash) |
在使用第三方库的时候,一定要仔细看它们提供的源码,里面应该有一些提供了给你用的函数,直接省了太多时间。
将时间字符串转换成time.Time
需要注意定制一下自己的时区
1 | timeZone := "Asia/Shanghai" |
如何对比 time.Time 变量的值
很容易犯的错误
1 | // Compare compares the time instant t with u. If t is before u, it returns -1; |
gb2312 -> utf8
1 | import iconv "github.com/djimenez/iconv-go" |
从通道中读取相同内容的问题,尤其是在涉及 slice []string
的情况下,可能是因为所有 goroutine 共享和修改了同一个切片实例,而没有进行正确的拷贝或同步。要理解这个问题,我们需要详细了解切片在 Go 中是如何工作的,以及它们在并发环境中的行为。
切片共享问题
在 Go 中,切片本质上是一个指向底层数组的引用。因此,当多个 goroutine 共享同一个切片时,如果一个 goroutine 修改了该切片的内容,其他 goroutine 会看到这些修改。以下是一些常见原因和解决方法:
共享切片实例:如果多个 goroutine 使用同一个切片实例并对其进行修改,那么它们会看到相同的内容。
没有进行拷贝:在向通道发送切片时,如果只是发送了切片的引用而没有进行深拷贝,那么接收方会看到切片的最新状态,而不是发送时的状态。
示例与解决方案
假设我们有一个通道,用于传递 []string
类型的切片。以下示例演示了可能出现的问题和如何解决它。
错误示例
下面的示例展示了共享切片导致的问题:
1 | package main |
输出可能如下:
1 | Received: [a2 b c] |
你会发现接收到的内容是相同的,这是因为发送的是同一个切片实例,每次修改都影响到了之前发送的切片。
解决方案:深拷贝切片
为了避免这个问题,可以在发送切片之前进行深拷贝:
1 | package main |
输出将如下所示:
1 | Received: [a0 b c] |
这样每次发送到通道的是切片的一个副本,接收方不会受到后续修改的影响。
总结
当从通道中读取相同内容时,可能是因为切片在并发环境中被共享和修改。解决方法是发送切片的副本,而不是发送切片的引用。通过在发送到通道之前进行深拷贝,可以确保接收方接收到的是发送时的状态,而不是后续修改的状态。
实际操作
golang中如何将一个小数转换成字符串,最多显示6位,如果精度只有2位,就只显示2位?
在 Go 中,如果你想将一个小数转换成字符串并控制最多显示 6 位小数,但当精度只有 2 位时只显示 2 位,可以使用 fmt 包中的 fmt.Sprintf 或 strconv.FormatFloat 方法来格式化浮点数。
这里是具体的实现方式:
方法 1:使用 fmt.Sprintf
fmt.Sprintf 提供了灵活的格式化选项。你可以通过 %.6f 格式限定最多显示 6 位小数,同时结合 strings.TrimRight 和 strings.TrimSuffix 去除多余的零。
package main
import (
“fmt”
“strings”
)
func FormatFloat(f float64) string {
s := fmt.Sprintf(“%.6f”, f) // 格式化为 6 位小数
s = strings.TrimRight(s, “0”) // 去掉末尾的多余零
s = strings.TrimSuffix(s, “.”) // 如果最后是小数点,去掉小数点
return s
}
func main() {
fmt.Println(FormatFloat(123.456)) // 输出 “123.456”
fmt.Println(FormatFloat(123.4)) // 输出 “123.4”
fmt.Println(FormatFloat(123.000001)) // 输出 “123.000001”
fmt.Println(FormatFloat(123.00)) // 输出 “123”
}
方法 2:使用 strconv.FormatFloat
strconv.FormatFloat 允许你指定浮点数的格式化规则。你可以使用 strconv.FormatFloat 来控制小数点后的精度。
package main
import (
“fmt”
“strconv”
)
func FormatFloat(f float64) string {
// 使用 ‘f’ 格式,保留最多 6 位小数,但删除无效的零
return strconv.FormatFloat(f, ‘f’, -1, 64)
}
func main() {
fmt.Println(FormatFloat(123.456)) // 输出 “123.456”
fmt.Println(FormatFloat(123.4)) // 输出 “123.4”
fmt.Println(FormatFloat(123.000001)) // 输出 “123.000001”
fmt.Println(FormatFloat(123.00)) // 输出 “123”
}
方法对比
• fmt.Sprintf 的方式可以让你灵活地控制格式化后删除尾随的零和小数点。
• strconv.FormatFloat 自动去除无效的零,但你无法精确指定最大显示的小数位数。
总结
• 如果你希望显示最多 6 位小数,并在必要时去除无效的尾零和小数点,fmt.Sprintf 是一个非常灵活的选择。
• strconv.FormatFloat 也能自动去除无效的尾零并保留必要的精度。